﻿using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using jSLCore.Service.Workflow;
using jSLCore.Service.Workflow.Linq;

namespace jSLCore.Service.Security
{
    /// <summary>
    /// Security based on jDatabase sql database
    /// </summary>
// ReSharper disable InconsistentNaming
    public class jDatabaseSecurityManager : ISecurityManager
// ReSharper restore InconsistentNaming
                                            , IPermissionDescriptionProvider
                                            , IEnumDescriptionProvider
                                            , ISuperVisorManager
                                            , IPasswordManager
    {
        private readonly string _connectionString;

        public jDatabaseSecurityManager(string connectionString)
        {
            _connectionString = connectionString;
        }

        #region IEnumDescriptionProvider Members

        public EnumDescription[] GetAllEnumDescriptions()
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                return db.EnumDescriptions.ToArray();
            }
        }

        #endregion

        #region IPasswordManager Members

        public void ChangeCurrentUserPassword(string userName, string oldPwd, string newPwd)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                var user = db.Users.GetUserByLoginAndPwdOrNull(userName, oldPwd);
                if (user == null)
                {
                    jWorkflowException.ThrowNew(
                        "Пользователь с указанным именем и паролем не найден. Возможно пароль введен не правильно");
                    throw new InvalidProgramException();
                }
                user.Password = new Binary(jSLAuthorizationServiceBase.GetPasswordHash(newPwd));
                db.SubmitChanges();
            }
        }

        #endregion

        #region IPermissionDescriptionProvider Members

        public PermissionDescription[] GetAllPermissionDescriptions()
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                return db.PermissionDescriptions.ToArray();
            }
        }

        #endregion

        #region ISecurityManager Members

        public User Authorize(string login, string password)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                User user = null;
                try
                {
                    user = db.Users.GetUserByLoginAndPwdOrNull(login, password);

                    if (user == null) throw new InvalidOperationException();
                    //user = new User(){IsEnabled = true,Login = loginName,Password = pwdHash};//создаем пользователя
                }
                catch (InvalidOperationException)
                {
                    jWorkflowException.ThrowNew("Имя пользователя или пароль введены неправильно");
                }

                //db.Users.InsertOnSubmit(user);
                //db.SubmitChanges();

// ReSharper disable PossibleNullReferenceException
                if (!user.IsEnabled)
                    jWorkflowException.ThrowNew("Ваш аккаунт заблокирован. Обратитесь к администратору.");
// ReSharper restore PossibleNullReferenceException

                Guid remeberCode = Guid.NewGuid(); //everytime user auth by login/pwd.. the remebercode will be reseted
                user.RemeberCode = remeberCode;
                db.SubmitChanges();
                db.Refresh(RefreshMode.OverwriteCurrentValues, user);
                return user;
            }
        }

        public User AuthorizeByCode(Guid code)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                User user;
                try
                {
                    user = db.Users.SingleOrDefault(u => u.RemeberCode == code);

                    if (user == null) throw new InvalidOperationException();
                    //user = new User(){IsEnabled = true,Login = loginName,Password = pwdHash};//создаем пользователя
                }
                catch (InvalidOperationException)
                {
                    //jWorkflowException.ThrowNew("Невозможно авторизоваться"); do nothing... user is out of login silently
                    return null;
                }

                //db.Users.InsertOnSubmit(user);
                //db.SubmitChanges();
                if (!user.IsEnabled)
                {
//jWorkflowException.ThrowNew("Ваш аккаунт заблокирован. Обратитесь к администратору."); check upper comment
                    return null;
                }
                return user;
            }
        }

        public int[] GetPermissionSet(string userName)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                User user = db.Users.GetOneOrNull(userName, "Login");
                if (user == null) return new int[0];
                return user.GetPermissionSet();
            }
        }

        public User GetUser(string login)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                User user = db.Users.GetOneOrjWorkflowException(login, "Пользователь", login, "Login");
                return user;
            }
        }

        public bool IsUserHasPermission(string userName, int permission)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                User user = db.Users.SingleOrDefault(u => u.Login == userName);
                return IsUserHasPermission(user, permission);
            }
        }

        #endregion

        #region ISuperVisorManager Members

        public PermissionDescription[] GetInheritedPermissions(Guid roleId)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                Role role = db.Roles.GetOneOrjWorkflowException(roleId, "Роль", "");

                IEnumerable<Permission> permissions = role
                    .GetAllRoles()
                    .SelectMany(r => r.Permissions)
                    .Distinct();

                return permissions
// ReSharper disable AccessToDisposedClosure
                    .Select(p => db.PermissionDescriptions.GetOne(p.PermissionNumber, "Permission"))
// ReSharper restore AccessToDisposedClosure
                    .ToArray();
            }
        }

        public Role GetRole(Guid id)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                Role role = db.Roles.GetOneOrjWorkflowException(id, "Роль", "");
                role.BeforeSending(db);
                return role;
            }
        }

        public Role[] SearchRoles(string searchText)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                Role[] res = db.Roles.Where(r => r.Name.Contains(searchText)).ToArray();
                foreach (Role role in res)
                {
                    role.BeforeSending(db);
                }
                return res;
            }
        }

        public void CreateRole(string name)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                var role = new Role {Name = name};
                db.Roles.InsertOnSubmit(role);
                db.SubmitChanges();
            }
        }

        public void DeleteRole(Guid id)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                Role role = db.Roles.GetOneOrjWorkflowException(id, "Роль", "");
                db.Roles.DeleteOnSubmit(role);
                db.SubmitChanges();
            }
        }

        public void SaveRole(Role role)
        {
            using (var db = new SecurityDatabaseDataContext(_connectionString))
            {
                var one = role.id == Guid.Empty ? new Role() : db.Roles.GetOneOrjWorkflowException(role.id, "Роль", role.Name);

                one.Name = role.Name;

                //Проверка циркуляции
                foreach (Role baseRole in role.BaseRoles)
                {
                    Role currentRole = db.Roles.GetOneOrjWorkflowException(baseRole.id, "Роль", baseRole.Name);

                    if (currentRole.GetAllRoles().Any(r => r.id == role.id))
                        jWorkflowException.ThrowNew(
                            "Невозможно сохранить роль, так как найдена циркулирующая ссылка. Для устранения конфликта необходимо исключить роль: " +
                            baseRole.Name);
                }

                SynchronizePermissions(role, db, one);
                SynchronizeBaseRoles(role, db, one);

                db.SubmitChanges();
            }
        }

        #endregion

        public static void InitializejSLAuthorizationServiceBase(string connectionString)
        {
            jSLAuthorizationServiceBase.InitializeSecurity(new jDatabaseSecurityManager(connectionString));
        }

        internal static bool IsUserHasPermission(User user, int permission)
        {
            if (user == null) return false;
            return user.HasPermission(permission);
        }

        private void SynchronizeBaseRoles(Role source, SecurityDatabaseDataContext db, Role target)
        {
            //Удаляем несуществующее наследие
            IEnumerable<RoleInherit> toDel =
                target.RoleInherits1.Where(r => !source.BaseRoles.Any(br => br.id == r.ParentRoleId));
            if (toDel.Any())
                db.RoleInherits.DeleteAllOnSubmit(toDel);
            //Добавляем новое наследие
            IEnumerable<Role> toAdd =
                source.BaseRoles.Where(br => !target.RoleInherits1.Any(t => t.ParentRoleId == br.id));

            foreach (Role role in toAdd)
            {
                var roleInherit = new RoleInherit();
                roleInherit.ChildRole = target;
                roleInherit.ParentRole = db.Roles.GetOneOrjWorkflowException(role.id, "Роль", role.Name);
                db.RoleInherits.InsertOnSubmit(roleInherit);
            }
        }

        private static void SynchronizePermissions(Role source, SecurityDatabaseDataContext db, Role target)
        {
            IEnumerable<Permission> forDel =
                target.Permissions.Where(p => !source.PermissionCash.Any(c => c.Permission == p.PermissionNumber));
            if (forDel.Any())
                db.Permissions.DeleteAllOnSubmit(forDel);
            //добавляем те, которые не перечислены в цели
            IEnumerable<PermissionDescription> toAdd =
                source.PermissionCash.Where(pc => !target.Permissions.Any(p => p.PermissionNumber == pc.Permission));
            foreach (PermissionDescription description in toAdd)
            {
                var permission = new Permission {PermissionNumber = description.Permission, Role = target};
                db.Permissions.InsertOnSubmit(permission);
            }
        }
    }
}